//
// video_select_server.c -- a video select server on TCP
//

#include <video_select_server.h>

// get sockaddr, IPv4 or IPv6:
void *get_in_addr(struct sockaddr *sa)
{
        if (sa->sa_family == AF_INET) {
                return &(((struct sockaddr_in*)sa)->sin_addr);
        }

        return &(((struct sockaddr_in6*)sa)->sin6_addr);
}

/*
* returns the client's bind object by its file descriptor
*/
struct video_client_bind_t *get_bind_by_fd(struct video_server_t *server, int client_fd) {
	struct video_client_bind_t *bind;

	// locate the bind in the hashtable
        HASH_FIND_INT(server->client_bindings, &client_fd, bind);
        if (bind == NULL) {
                perror("trying to retrieve an unknown client\n");
                exit(-1);
        }

	return bind;
}

/*
* Creates a new bind object for a new client & binds it to a video player
*/
int register_new_client(struct video_server_t *server, int client_fd) {
	struct video_client_bind_t *bind;

	// create a new bind
	bind = (struct video_client_bind_t*) malloc(sizeof(struct video_client_bind_t));
	bind->client_fd = client_fd;
	bind->buffer_pos = 0;
	// set the packet queue in the bind
	packet_queue_init(&(bind->queue));
	
	// try to bind it to a video screen
	if (server->player_events->on_client_connected(bind) != SUCCESSFUL_BIND) {
		printf("failed binding new client to video picture\n");
		return -1;
	}

	// add the bind object to the hashtable (the key is the client's file descriptor)
	HASH_ADD_INT(server->client_bindings, client_fd, bind);

	return 0;	
}

/*
* Break the player-client bind
*/
int unregister_client(struct video_server_t *server, int client_fd) {
	struct video_client_bind_t *bind;
	// get the relevant bind
	bind = get_bind_by_fd(server, client_fd);

	// try to close the player
	if (server->player_events->on_client_disconnected(bind) != SUCCESSFUL_UNBIND) {
		perror("failed unbinding client");
		exit(-1);
	}
	// remove the bind from the hashtable
	HASH_DEL(server->client_bindings, bind);

	free(bind);
	bind = NULL;

	return 0;
}

/*
* Checks if a new AVPacket is available in the bytes read from the client to its bind object
*/
int is_avpacket_available(struct video_client_bind_t *bind) {
	int packet_header_size = sizeof(int64_t) * 4 + sizeof(int) * 4;
	int packet_content_size_location = sizeof(int64_t) * 2;
	int size;

	// if the buffer pos is smaller than the header size it couldn't be complete
	if (bind->buffer_pos < packet_header_size)
		return 0;

	// get the inner buffer size from the header
	size = *((int*) (bind->buffer + packet_content_size_location));

	// check if the packet is complete
	if (bind->buffer_pos >= packet_header_size + size) {
		return 1;
	}

	return 0;
}

/*
* Reconstructs the packet from the buffer that was read
*/ 
void reconstruct_avpacket(struct video_client_bind_t *bind) {
	AVPacket pkt;
	int offset = 0, new_len = 0;
	av_init_packet(&pkt);

	pkt.pts = *((int64_t*)(bind->buffer + offset));	// packet pts
	offset += sizeof(pkt.pts);
	pkt.dts = *((int64_t*)(bind->buffer + offset)); 	// packet dts
	offset += sizeof(pkt.dts);
	pkt.size = *((int*)(bind->buffer + offset));		// packet content size
	offset += sizeof(pkt.size);
	pkt.data = malloc(pkt.size);				// packet content
	memcpy(pkt.data, (void*)(bind->buffer + offset), pkt.size);
	offset += pkt.size;
	pkt.stream_index = *((int*)(bind->buffer + offset));	// packet stream index
	offset += sizeof(pkt.stream_index);
	pkt.flags = *((int*)(bind->buffer + offset));		// packet flags
	offset += sizeof(pkt.flags);
	pkt.duration = *((int*)(bind->buffer + offset));	// packet duration
	offset += sizeof(pkt.flags);
	pkt.pos = *((int64_t*)(bind->buffer + offset));	// packet position
	offset += sizeof(pkt.pos);
	pkt.convergence_duration = *((int64_t*)(bind->buffer + offset)); // packet convergence duration
	offset += sizeof(pkt.convergence_duration);

	// put the new AVPacket in the bind's queue
	packet_queue_put(&bind->queue, &pkt);
	av_free_packet(&pkt);

	// move data back on the buffer vector
	new_len = bind->buffer_pos - offset;
	memmove((void*) bind->buffer, (void*) (bind->buffer + offset), new_len);
	bind->buffer_pos = new_len;
}

/*
* Appends the bytes last written to the server to the relevant's client buffer
*/
void append_queue_buffer(struct video_server_t *server, int client_fd)
{
	struct video_client_bind_t *bind;
	bind = get_bind_by_fd(server, client_fd);

	// make sure the client's buffer isn't too full, if it is there must have been something wrong
	if ((server->bytes_read + bind->buffer_pos) >= MAX_AVPACKET_SIZE) {
		perror("not enough space in buffer for packet!\n");
		exit(-1);
	} 

	// copy the buffer from the server buffer to the client's bind buffer
	memcpy((void*)(bind->buffer + bind->buffer_pos), (void*)(server->read_buffer), server->bytes_read);
	bind->buffer_pos += server->bytes_read;

	// reconstruct an AVPacket if we got enough bytes
	while(is_avpacket_available(bind)) {
		reconstruct_avpacket(bind);
	}
}

/*
* Constructs a new TCP video select server
* player_events - a struct that holds the delegates that for creating an closing a new client-video bind
*/
void video_server(struct video_server_t *my_server, struct video_player_events_t *player_events)
{
    	int yes=1;        // for setsockopt() SO_REUSEADDR, below
    	int rv;
        struct addrinfo hints, *ai, *p;
	my_server->player_events = player_events;
    	FD_ZERO(&(my_server->master));    // clear the master and temp sets

	// get us a socket and bind it
        memset(&hints, 0, sizeof hints);
        hints.ai_family = AF_UNSPEC;
        hints.ai_socktype = SOCK_STREAM;
        hints.ai_flags = AI_PASSIVE;
        if ((rv = getaddrinfo(NULL, PORT, &hints, &ai)) != 0) {
                fprintf(stderr, "selectserver: %s\n", gai_strerror(rv));
                exit(1);
        }

	// init the listener
        for(p = ai; p != NULL; p = p->ai_next) {
	        my_server->listener = socket(p->ai_family, p->ai_socktype, p->ai_protocol);
                if (my_server->listener < 0) {
                        continue;
                }

                // lose the pesky "address already in use" error message
                setsockopt(my_server->listener, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int));

                if (bind(my_server->listener, p->ai_addr, p->ai_addrlen) < 0) {
                        close(my_server->listener);
                        continue;
                }

                break;
        }

        // if we got here, it means we didn't get bound
        if (p == NULL) {
                fprintf(stderr, "selectserver: failed to bind\n");
                exit(2);
	}

	freeaddrinfo(ai); // all done with this

	// keep track of the biggest file descriptor
	my_server->fdmax = my_server->listener;
	my_server->is_init = 1;
	my_server->is_running = 0;
}

/*
* Starts listening for connections
*/
int video_server_listen(struct video_server_t *server)
{
	if (!server) {
		perror("null server");
		return -1;
	}
	
	if (!server->is_init) {
		perror("server not initialized");
		server->error_code = ERR_SERVER_NOT_INIT;
		return -1;
	}

	// listen
    	if (listen(server->listener, 10) == -1) {
        	perror("listen");
        	exit(3);
    	}

    	// add the listener to the master set
    	FD_SET(server->listener, &(server->master));

    	return 0;
}

/*
* Runs the TCP Server to handle connections & arriving buffers
*/
void* video_server_run(void* args) {
	fd_set read_fds;
	int newfd;        // newly accepted socket descriptor
        struct sockaddr_storage remoteaddr; // client address
        socklen_t addrlen;
	struct timeval tv;
        char remoteIP[INET6_ADDRSTRLEN];
        int i;
	struct video_server_t *server = (struct video_server_t*) args;
	FD_ZERO(&read_fds);

	// select timeout (2 secs)	
	tv.tv_sec = 2;
	tv.tv_usec = 0;
	
	// main loop
        while(server->is_running) {
                read_fds = server->master; // copy it
		// wait for something to happen
                if (select(server->fdmax+1, &read_fds, NULL, NULL, &tv) == -1) {
                        perror("select");
                        exit(4);
                }

		if (!server->is_running) {
			return;
		}

                // run through the existing connections looking for data to read
                for(i = 0; i <= server->fdmax; i++) {
                        if (FD_ISSET(i, &read_fds)) { // we got one!!
                                if (i == server->listener) {
                                        // handle new connections
                                        addrlen = sizeof(remoteaddr);
                                        newfd = accept(server->listener,
                                                (struct sockaddr *)&remoteaddr,
                                                &addrlen);

                                        if (newfd == -1) {
                                                perror("accept");
                                        }
                                        else {
                                                printf("selectserver: new connection from %s on "
                                                       "socket %d\n",
                                                       inet_ntop(remoteaddr.ss_family, get_in_addr((struct sockaddr*)&remoteaddr), remoteIP, INET6_ADDRSTRLEN), newfd);

						// try to register the new client
	                                        if (register_new_client(server, newfd) < 0) {
							printf("failed binding to player, closing connection\n");
							close(newfd);
						}
			          		else {
							FD_SET(newfd, &(server->master)); // add to master set
		                                        if (newfd > server->fdmax) {    // keep track of the max
								server->fdmax = newfd;
							}
						}
					}
                                }
                                else {
					// read data from client
                                        if ((server->bytes_read = recv(i, server->read_buffer, sizeof(server->read_buffer), 0)) <= 0) {
                                                // got error or connection closed by client
                                                if (server->bytes_read == 0) {
                                                        // connection closed
                                                        printf("selectserver: socket %d hung up\n", i);
                                                }
                                                else {
                                                        perror("recv");
                                                }
                                                close(i); // bye!
                                                FD_CLR(i, &(server->master)); // remove from master set
						// bye bye client
						unregister_client(server, i);
                                        }
                                        else {
                                                // we got some data from client 'i'
						// appending data to its queue
                                                append_queue_buffer(server, i);
                                        }
                                } // END handle data from client
                        } // END got new incoming connection
                } // END looping through file descriptors
        } // END for(;;)--and you thought it would never end!
}

/*
* Starts running the server on a different thread
*/
int video_server_start(struct video_server_t *server) {
        if (server->is_running) {
                perror("cannot start an already started server");
                server->error_code = ERR_SERVER_ALREADY_RUNNING;
                return -1;
        }

        if (video_server_listen(server) < 0) {
                perror("failed listening");
                return -1;
        }

	server->is_running = 1;
        pthread_create(&server->server_thread, NULL, video_server_run, (void *) server);

        return 0;
}

/*
* Stops the running server 
*/
int video_server_stop(struct video_server_t *server) {
	int i;	

	if (!server) {
		perror("null server");
		return -1;
	}

	if (!server->is_running) {
                perror("cannot stop a stopped server");
                server->error_code = ERR_SERVER_NOT_RUNNING;
                return -1;
        }

	server->is_running = 0;

	// Close listening socket
	close(server->listener);
	FD_CLR(server->listener, &server->master);
	// Close all other sockets
	for(i = 0; i <= server->fdmax; i++) {
		if (FD_ISSET(i, &server->master)) {
			close(i);
			FD_CLR(i, &server->master);
			unregister_client(server, i);
		}
	}
	server->fdmax = 0;	
	
	// wait for select thread to return	
	pthread_join(server->server_thread, NULL);

        return 0;
}

/*
* Destructor
*/
int video_server_close(struct video_server_t *server) {
	if (!server) {
		perror("null server");
		return -1;
	}

	if (server->is_running && video_server_stop(server) < 0) {
		perror("could not stop server");
		return -1;
	}

	free(server);
	server = NULL;

	return 0;
}
